﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EntryViewModel.cs" company="Marcin Jastrzebski">
//   Copyright (c) Marcin Jastrzebski (marcin@marcinj.net)
// </copyright>
// <summary>
//   The entry page view model.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace mPassRT.ViewModels.Entry
{
    using System;
    using System.Collections.Generic;
    using System.Windows.Input;

    using KeePassLib;
    using KeePassLib.Cryptography;
    using KeePassLib.Interfaces;
    using KeePassLib.Security;
    using KeePassLib.Utility;

    using Microsoft.Practices.Unity;

    using mPassRT.Common;
    using mPassRT.Services;
    using mPassRT.Utilities;

    using Windows.ApplicationModel.DataTransfer;

    /// <summary>The entry page view model.</summary>
    public class EntryViewModel : PageViewModel
    {
        #region Fields

        /// <summary>The discard command.</summary>
        private readonly Command discardCommand;

        /// <summary>The save command.</summary>
        private readonly Command saveCommand;

        /// <summary>The current entry.</summary>
        private PwEntry currentEntry;

        /// <summary>The database.</summary>
        private PwDatabase database;

        /// <summary>The notes.</summary>
        private string notes;

        /// <summary>The password.</summary>
        private string password;

        /// <summary>The tags.</summary>
        private string tags;

        /// <summary>The title.</summary>
        private string title;

        /// <summary>The unsaved changes present.</summary>
        private bool unsavedChangesPresent;

        /// <summary>The url.</summary>
        private string url;

        /// <summary>The user name.</summary>
        private string userName;

        #endregion

        #region Constructors and Destructors

        /// <summary>Initializes a new instance of the <see cref="EntryViewModel"/> class.</summary>
        public EntryViewModel()
        {
            this.discardCommand = new Command
                                      {
                                          ExecuteHandler = this.OnRevertCommandExecute, 
                                          CanExecuteHandler = _ => this.UnsavedChangesPresent
                                      };

            this.saveCommand = new Command
                                   {
                                       ExecuteHandler = this.OnSaveCommandExecute, 
                                       CanExecuteHandler = _ => this.UnsavedChangesPresent
                                   };
        }

        #endregion

        #region Public Properties

        /// <summary>Gets the copy password command.</summary>
        public ICommand CopyPasswordCommand
        {
            get
            {
                return new Command { ExecuteHandler = this.CopyPassword };
            }
        }

        /// <summary>Gets or sets the database loader.</summary>
        [Dependency]
        public IDatabaseLoader DatabaseLoader { get; set; }

        /// <summary>Gets the discard command.</summary>
        public ICommand DiscardCommand
        {
            get
            {
                return this.discardCommand;
            }
        }

        /// <summary>Gets the generate password command.</summary>
        public ICommand GeneratePasswordCommand
        {
            get
            {
                return new Command { ExecuteHandler = this.GeneratePassword };
            }
        }

        /// <summary>Gets or sets the navigator.</summary>
        [Dependency]
        public INavigator Navigator { get; set; }

        /// <summary>Gets or sets the notes.</summary>
        public string Notes
        {
            get
            {
                return this.notes;
            }

            set
            {
                if (this.SetProperty(ref this.notes, value))
                {
                    this.UnsavedChangesPresent = true;
                }
            }
        }

        /// <summary>Gets or sets the password.</summary>
        public string Password
        {
            get
            {
                return this.password;
            }

            set
            {
                if (this.SetProperty(ref this.password, value))
                {
                    this.OnPropertyChanged("PasswordQuality");
                    this.UnsavedChangesPresent = true;
                }
            }
        }

        /// <summary>Gets or sets the password generator.</summary>
        [Dependency]
        public IPasswordGenerator PasswordGenerator { get; set; }

        /// <summary>Gets the password quality.</summary>
        public uint PasswordQuality
        {
            get
            {
                char[] chars = (this.Password ?? string.Empty).ToCharArray();
                uint bits = QualityEstimation.EstimatePasswordBits(chars);
                Array.Clear(chars, 0, chars.Length);

                return bits;
            }
        }

        /// <summary>Gets the save command.</summary>
        public ICommand SaveCommand
        {
            get
            {
                return this.saveCommand;
            }
        }

        /// <summary>Gets or sets the tags.</summary>
        public string Tags
        {
            get
            {
                return this.tags;
            }

            set
            {
                this.SetProperty(ref this.tags, value);
                this.UnsavedChangesPresent = true;
            }
        }

        /// <summary>Gets or sets the title.</summary>
        public string Title
        {
            get
            {
                return this.title;
            }

            set
            {
                if (this.SetProperty(ref this.title, value))
                {
                    this.UnsavedChangesPresent = true;
                }
            }
        }

        /// <summary>Gets or sets a value indicating whether unsaved changes are present.</summary>
        public bool UnsavedChangesPresent
        {
            get
            {
                return this.unsavedChangesPresent;
            }

            set
            {
                this.SetProperty(ref this.unsavedChangesPresent, value);
                this.discardCommand.OnCanExecuteChanged();
                this.saveCommand.OnCanExecuteChanged();
            }
        }

        /// <summary>Gets or sets the url.</summary>
        public string Url
        {
            get
            {
                return this.url;
            }

            set
            {
                if (this.SetProperty(ref this.url, value))
                {
                    this.UnsavedChangesPresent = true;
                }
            }
        }

        /// <summary>Gets or sets the user name.</summary>
        public string UserName
        {
            get
            {
                return this.userName;
            }

            set
            {
                if (this.SetProperty(ref this.userName, value))
                {
                    this.UnsavedChangesPresent = true;
                }
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>Loads state.</summary>
        /// <param name="navigationParameter">The navigation parameter.</param>
        /// <param name="state">The state.</param>
        public override async void LoadState(object navigationParameter, Dictionary<string, object> state)
        {
            var navStr = navigationParameter as string;
            if (navStr == null)
            {
                return;
            }

            var parameter = SerializationUtility.DeserializeFromString<DatabaseNavigationParameter>(navStr);
            if (parameter == null)
            {
                throw new ArgumentException("Invalid navigation parameter.");
            }

            this.database = await this.DatabaseLoader.GetDatabaseAsync(parameter.FilePath);
            if (this.database == null)
            {
                this.Navigator.GoBack();
                return;
            }

            this.currentEntry = GetEntry(this.database, parameter.ItemId);
            if (this.currentEntry == null)
            {
                throw new Exception("The entry could not be found.");
            }

            this.LoadChanges();
        }

        #endregion

        #region Methods

        /// <summary>Gets the requested entry.</summary>
        /// <param name="database">The database.</param>
        /// <param name="itemId">The item id.</param>
        /// <returns>The <see cref="PwEntry"/>.</returns>
        private static PwEntry GetEntry(PwDatabase database, string itemId)
        {
            if (itemId == null)
            {
                throw new ArgumentNullException("itemId");
            }

            return database.RootGroup.FindEntry(new PwUuid(MemUtil.HexStringToByteArray(itemId)), true);
        }

        /// <summary>Copies the password to the clipboard.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private void CopyPassword(object obj)
        {
            var package = new DataPackage();
            package.SetText(this.Password);
            Clipboard.SetContent(package);
        }

        /// <summary>Generates a new password.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private async void GeneratePassword(object obj)
        {
            string generatedPassword = await this.PasswordGenerator.GeneratePasswordAsync();
            if (generatedPassword != null)
            {
                this.Password = generatedPassword;
            }
        }

        /// <summary>Loads changes from the entry in the DB.</summary>
        private void LoadChanges()
        {
            this.Title = this.currentEntry.GetString(PwDefs.TitleField, true);
            this.UserName = this.currentEntry.GetString(PwDefs.UserNameField, false);
            this.Password = this.currentEntry.GetString(PwDefs.PasswordField, false);
            this.Url = this.currentEntry.GetString(PwDefs.UrlField, false);
            this.Notes = this.currentEntry.GetString(PwDefs.NotesField, false);
            this.Tags = StrUtil.TagsToString(this.currentEntry.Tags, true);

            // must be last
            this.UnsavedChangesPresent = false;
        }

        /// <summary>Reverts changes made by the user.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private void OnRevertCommandExecute(object obj)
        {
            this.LoadChanges();
        }

        /// <summary>Saves changes made by the user.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private void OnSaveCommandExecute(object obj)
        {
            this.SaveChanges();
        }

        /// <summary>Saves changes.</summary>
        private void SaveChanges()
        {
            var mpc = new MemoryProtectionConfig();
            this.currentEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(mpc.ProtectTitle, this.Title));
            this.currentEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(mpc.ProtectUserName, this.UserName));
            this.currentEntry.Strings.Set(PwDefs.PasswordField, new ProtectedString(mpc.ProtectPassword, this.Password));
            this.currentEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(mpc.ProtectUrl, this.Url));
            this.currentEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(mpc.ProtectNotes, this.Notes));
            this.currentEntry.Tags = StrUtil.StringToTags(this.Tags);

            this.database.Save(new NullStatusLogger());
            this.UnsavedChangesPresent = false;
        }

        #endregion
    }
}